module Project2(SW,KEY,LEDR,LEDG,HEX0,HEX1,HEX2,HEX3,CLOCK_50);
  input  [9:0] SW;
  input  [3:0] KEY;
  input  CLOCK_50;
  output [9:0] LEDR;
  output [7:0] LEDG;
  output [6:0] HEX0,HEX1,HEX2,HEX3;

  parameter DBITS    =32;
  parameter INSTSIZE =32'd4;
  parameter INSTBITS =32;
  parameter STARTPC  =32'h20;
  parameter REGNOBITS=5;
  parameter ADDRKEY  =32'hFFFFFF00;
  parameter ADDRSW   =32'hFFFFFF10;
  parameter ADDRHEX =32'hFFFFFF80;
  parameter ADDRLEDR=32'hFFFFFF90;
  parameter ADDRLEDG=32'hFFFFFFA0;
  parameter IMEMINITFILE="Sorter2.mif";
  parameter IMEMADDRBITS=13;
  parameter IMEMWORDBITS=2;
  parameter IMEMWORDS=2048;
  parameter DMEMADDRBITS=13;
  parameter DMEMWORDBITS=2;
  parameter DMEMWORDS=2048;
  
  parameter OP1_ALUR=6'o00;
  parameter OP1_ANDI=6'o01;
  parameter OP1_ORI =6'o02;
  parameter OP1_HI  =6'o03;
  parameter OP1_ADDI=6'o04;
  parameter OP1_LW  =6'o20;
  parameter OP1_SW  =6'o21;
  parameter OP1_JAL =6'o40;
  parameter OP1_BEQ =6'o41;
  parameter OP1_BNE =6'o45;
  
  parameter OP2_AND =6'o11;
  parameter OP2_OR  =6'o12;
  parameter OP2_XOR =6'o13;
  parameter OP2_NAND=6'o15;
  parameter OP2_NOR =6'o16;
  parameter OP2_NXOR=6'o17;
  parameter OP2_ADD =6'o20;
  parameter OP2_SUB =6'o21;
  parameter OP2_EQ  =6'o41;
  parameter OP2_LT  =6'o42;
  parameter OP2_LE  =6'o43;
  parameter OP2_NE  =6'o45;
  parameter OP2_GE  =6'o46;
  parameter OP2_GT  =6'o47;
  // This is not a valid opcode2 value, but it is a valid ALUfunc value
  parameter OP2_LOHI=6'o77;
  
  wire clk,lock;
  Pll pll(.inclk0(CLOCK_50),.c0 (clk),.locked(lock));
  wire reset=!lock;
  
  // Create the processor's bus
  tri [(DBITS-1):0] thebus;
  parameter BUSZ={DBITS{1'bZ}};
  
  // Create PC and connect it to the bus
  reg [(DBITS-1):0] PC;
  reg LdPC, DrPC, IncPC;
  always @(posedge clk) begin
    if(reset)
	   PC<=STARTPC;
	 else if(LdPC)
      PC<=thebus;
    else if(IncPC)
      PC<=PC+INSTSIZE;
  end
  assign thebus=DrPC?PC:BUSZ;

  // Create the instruction memory
  (* ram_init_file = IMEMINITFILE *)
  reg [(DBITS-1):0] imem[(IMEMWORDS-1):0];
  wire [(DBITS-1):0] iMemOut=imem[PC[(IMEMADDRBITS-1):IMEMWORDBITS]];
  
  wire [4:0] nextrx = iMemOut[25:21];

  // Create the IR (feeds directly from memory)
  reg [(INSTBITS-1):0] IR;
  reg LdIR;
  always @(posedge clk or posedge reset)
    if(reset)
	   IR<=32'b0;
	 else if(LdIR)
      IR <= iMemOut;
  // Output immediate value(s) to the bus
  // ShOff causes value to be left-shifted 2 places (this is *4 for B?? and JAL)
  reg ShOff,DrOff;
  wire [(DBITS-1):0] offval = ShOff ? {{14{IR[15]}},IR[15:0],2'b00} : {{16{IR[15]}},IR[15:0]};
  assign thebus = DrOff ? offval : BUSZ;
  wire [5:0] opcode1 = IR[31:26];
  wire [4:0] rx = IR[25:21], ry = IR[20:16], rz = IR[15:11];
  wire [5:0] opcode2 = IR[5:0];
  
  // Create the data memory and connect to the bus
  reg [(DBITS-1):0] MAR;  // MAR register
  reg LdMAR,WrMem,DrMem; // Control signals
  // Memory data input comes from the bus
  wire [(DBITS-1):0] memin=thebus; 
  // Connect MAR input to the bus
  always @(posedge clk or posedge reset)
    if(reset)
	   MAR<=32'b0;
    else if(LdMAR)
      MAR<=thebus;
  // Create and connect HEX register
  reg [15:0] HexOut;
  SevenSeg ss3(.OUT(HEX3),.IN(HexOut[15:12]));
  SevenSeg ss2(.OUT(HEX2),.IN(HexOut[11:8]));
  SevenSeg ss1(.OUT(HEX1),.IN(HexOut[7:4]));
  SevenSeg ss0(.OUT(HEX0),.IN(HexOut[3:0]));
  always @(posedge clk or posedge reset) begin
    if(reset)
	   HexOut <= 16'hDEAD;
	 else if(WrMem&&(MAR==ADDRHEX))
	   HexOut <= memin[15:0];
  end
		
  // TODO: Create and connect write logic for LEDR and LEDG 
  reg [9:0] LEDRout;
  assign LEDR = LEDRout;
  always @(posedge clk or posedge reset) begin
		if(reset) begin
			LEDRout <= 10'h0;
		end
		else if(WrMem && MAR == ADDRLEDR) begin
			LEDRout <= memin[9:0];
		end
  end
  
  reg [7:0] LEDGout;
  assign LEDG = LEDGout;
  always @(posedge clk or posedge reset) begin
		if(reset) begin
			LEDGout <= 8'h00;
		end
		else if(WrMem && MAR == ADDRLEDG) begin
			LEDGout <= memin[7:0];
		end
  end
  
  // Now the real data memory
  wire MemEnable=!(MAR[(DBITS-1):DMEMADDRBITS]);
  wire MemWE=lock&WrMem&MemEnable;
  reg [(DBITS-1):0] dmem[(DMEMWORDS-1):0];
  always @(posedge clk)
    if(MemWE)
      dmem[MAR[(DMEMADDRBITS-1):DMEMWORDBITS]]<=memin;
  wire [(DBITS-1):0] MemVal=MemWE?{DBITS{1'bX}}:dmem[MAR[(DMEMADDRBITS-1):DMEMWORDBITS]];
  // Connect output of memory and input devices to the bus
  wire [(DBITS-1):0] memout=
	MemEnable?MemVal:
	(MAR==ADDRKEY)?{12'b0,~KEY}:
	(MAR==ADDRSW)? { 6'b0,SW}:
	32'hDEADBEEF;
  assign thebus=DrMem?memout:BUSZ;
  
	//Start RegFile
	reg [4:0] regno;
	reg WrReg, DrReg;
	reg [31:0] regfile [31:0];
	always @(posedge clk) begin
		if(WrReg) begin
			regfile[regno] <= thebus;
		end
	end
	
	assign thebus = DrReg ? regfile[regno] : BUSZ;
	//End RegFile
  
  //Begin ALU
  reg signed [31:0] A = 32'b0, B = 32'b0, ALUout = 32'b0;
  reg [5:0] ALUfunc;
  always @(ALUfunc or A or B) begin
		case(ALUfunc)
			OP2_AND:		ALUout = A & B;
			OP2_OR:		ALUout = A | B;
			OP2_XOR:		ALUout = A ^ B;
			OP2_NAND:	ALUout = ~(A & B);
			OP2_NOR: 	ALUout = ~(A | B);
			OP2_NXOR:	ALUout = ~(A ^ B);
			OP2_ADD:		ALUout = A + B;
			OP2_SUB:		ALUout = A - B;
			OP2_EQ:		ALUout = (A == B);
			OP2_LT:		ALUout = (A < B);
			OP2_LE:		ALUout = (A <= B);
			OP2_NE:		ALUout = (A != B);
			OP2_GE:		ALUout = (A >= B);
			OP2_GT:		ALUout = (A > B);
			OP2_LOHI:	ALUout = {B[31:16],A[15:0]};
			default: 	ALUout = {32{1'bX}};
		endcase
	end
	reg LdA, LdB, DrALU;
	always @(posedge clk) begin
		if(LdA) begin
			A <= thebus;
		end
		
		if(LdB) begin
			B <= thebus;
		end
	end
	assign thebus = DrALU ? ALUout : BUSZ;
	//End ALU
	
	//Begin FSM
	parameter S_BITS=5;
	parameter [(S_BITS-1):0]
		S_ZERO  ={(S_BITS){1'b0}},
		S_ONE   ={{(S_BITS-1){1'b0}},1'b1},
		S_FETCH1=S_ZERO,				// 00
		S_FETCH2=S_FETCH1+S_ONE,  // 01
		S_ALUR1 =S_FETCH2+S_ONE,	// 02
		S_ALUR2 =S_ALUR1 +S_ONE,	// 03
		S_ALUI1 =S_ALUR2 +S_ONE,	// 04
		S_ALUI2 =S_ALUI1 +S_ONE,	// 05
		S_LSW1  =S_ALUI2 +S_ONE,	//	06
		S_LSW2  =S_LSW1  +S_ONE,	//	07
		S_LW3   =S_LSW2  +S_ONE,	//	08
		S_SW3   =S_LW3   +S_ONE,	//	09
		S_BRC1  =S_SW3   +S_ONE,	// 0A
		S_BRC2  =S_BRC1  +S_ONE,	// 0B
		S_BRC3  =S_BRC2  +S_ONE,	//	0C
		S_BRC4  =S_BRC3  +S_ONE,	// 0D
		// BRC5 is an addition, should be able to optimize away with good preloading
		S_BRC5  =S_BRC4  +S_ONE,  // 0E
		S_JAL1  =S_BRC5  +S_ONE,	//	0F
		S_JAL2  =S_JAL1  +S_ONE,	//	10
		S_JAL3  =S_JAL2  +S_ONE,	//	11
		S_ERROR =S_JAL3  +S_ONE;	//	12
	reg [(S_BITS-1):0] state,next_state;
	wire [5:0] aluf = (opcode1==OP1_ANDI) ? OP2_AND : 
							(opcode1==OP1_ORI) ? OP2_OR : 
							(opcode1==OP1_HI) ? OP2_LOHI :
							(opcode1==OP1_ADDI) ? OP2_ADD :
							(opcode1==OP1_BEQ) ? OP2_EQ : OP2_NE;
	always @(state or opcode1 or rx or ry or rz or opcode2 or ALUout[0] or aluf or nextrx) begin
		ALUfunc = OP2_ADD;
		{LdPC,DrPC,IncPC,LdMAR,WrMem,DrMem,LdIR,DrOff,ShOff, LdA, LdB,DrALU,regno,DrReg,WrReg,next_state}=
		{1'b0,1'b0, 1'b0, 1'b0, 1'b0, 1'b0,1'b0, 1'b0, 1'b0,1'b0,1'b0, 1'b0, 5'b0, 1'b0, 1'b0,state+S_ONE};
		
		case(state)
			// preloads: A <- rx, B <- sxt(imm)
			S_FETCH1: {LdIR,IncPC,regno,DrReg,LdA}={1'b1,1'b1,nextrx,1'b1,1'b1};
			S_FETCH2: begin
				{DrOff,LdB}={2'b11};
				case(opcode1)
					OP1_ALUR: next_state=S_ALUR1;
					OP1_ANDI: next_state=S_ALUI1;
					OP1_ORI:  next_state=S_ALUI1;
					OP1_HI:	 next_state=S_ALUI1;
					OP1_ADDI: next_state=S_ALUI1;
					OP1_LW: next_state=S_LSW1;
					OP1_SW: next_state=S_LSW1;
					OP1_JAL: next_state=S_JAL1;
					OP1_BEQ: next_state=S_BRC1;
					OP1_BNE: next_state=S_BRC1;
					default: next_state=S_ERROR;
				endcase
			end
			S_ALUR1: {regno,DrReg,LdB}={ry, 2'b11};
			S_ALUR2: {ALUfunc,DrALU,regno,WrReg,next_state}={opcode2,1'b1,rz,1'b1,S_FETCH1};
			S_ALUI1: {regno,DrReg,LdA}={rx,1'b1,1'b1};
			S_ALUI2: {ALUfunc,DrALU,regno,WrReg,next_state}={aluf,1'b1,ry,1'b1,S_FETCH1};
			S_LSW1: {ALUfunc,DrALU,LdMAR}={OP2_ADD,2'b11};
			S_LSW2: next_state = (opcode1==OP1_LW) ? S_LW3 : S_SW3;
			S_LW3: {regno,WrReg,DrMem,next_state}={ry,2'b11,S_FETCH1};
			S_SW3: {regno,DrReg,WrMem,next_state}={ry,2'b11,S_FETCH1};
			// if(rx cmp ry) PC=PC+4+(sxt(imm)*4)
			S_BRC1: {regno,DrReg,LdB}={ry,2'b11};
			// not sure if the branch decision belongs in BRC2... check here if it doesn't work
			S_BRC2: {ALUfunc,DrALU,next_state}={aluf,1'b1,(ALUout[0]) ? S_BRC3 : S_FETCH1};
			S_BRC3: {ShOff,DrOff,LdA}={3'b111};
			S_BRC4: {DrPC,LdB}={2'b11};
			S_BRC5: {ALUfunc,DrALU,LdPC,next_state}={OP2_ADD,2'b11,S_FETCH1};
			// ry<=PC+4; PC<=rx+(sxt(imm)*4) simultaneously!
			S_JAL1: {regno,DrPC,WrReg}={ry,2'b11};
			S_JAL2: {ShOff,DrOff,LdB}={3'b111};
			S_JAL3: {ALUfunc,DrALU,LdPC,next_state}={OP2_ADD,2'b11,S_FETCH1};
			default:  next_state=S_ERROR;
		endcase
	end
	always @(posedge clk or posedge reset)
		if(reset)
			state<=S_FETCH1;
		else
			state<=next_state;
endmodule
